package jflowmap.views.quadrant;

import java.awt.Font;
import java.awt.Stroke;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import jflowmap.model.graph.Edge;
import jflowmap.model.graph.Node;
import jflowmap.model.ontology.Context;
import jflowmap.model.ontology.Dimension;
import jflowmap.model.ontology.SelectionListener;
import jflowmap.model.ontology.Value;
import jflowmap.util.Colors;
import jflowmap.util.box2d.DynamicallyPlaceable;
import jflowmap.util.piccolo.PPaths;
import edu.umd.cs.piccolo.PCamera;
import edu.umd.cs.piccolo.PNode;
import edu.umd.cs.piccolo.event.PBasicInputEventHandler;
import edu.umd.cs.piccolo.event.PInputEvent;
import edu.umd.cs.piccolo.nodes.PPath;
import edu.umd.cs.piccolox.util.PFixedWidthStroke;

public class Spiral extends PNode implements SelectionListener {
	private static final long serialVersionUID = 1L;
	
	private static final Stroke ARROW_STROKE = new PFixedWidthStroke(3);
	
	public static class ValuePair {
		private Value x;
		private Value y;
		
		public Value getX() {
			return x;
		}
		
		public Value getY() {
			return y;
		}
		
		@Override
		public String toString() {
			return "ValuePair [x=" + x + ", y=" + y + "]";
		}
		
		public boolean isValid() {
			return Context.missingValue != x && Context.missingValue != y;
		}
		
		public Point2D toPoint2D() {
			return new Point2D.Double(x.getValue(), y.getValue());
		}
		
		private PNode dotNode;
	}
	
	private final Node node;
	private final VisualSpiral visualSpiral;
	
	private final Map<Dimension, ValuePair> values = new HashMap<Dimension, ValuePair>();
	private final List<PPath> arrows;
	
	private double minX = Double.MAX_VALUE;
	private double maxX = Double.MIN_VALUE;
	private double minY = Double.MAX_VALUE;
	private double maxY = Double.MIN_VALUE;
	private double avgX;
	private double avgY;
	
	private final Dimension xDimension;
	private final Dimension yDimension;

	private boolean selected = false;
	private final List<SpiralDescription> descriptions = new Vector<SpiralDescription>();
	private final List<Blocker> blockers = new Vector<Blocker>();
	
	public Spiral(final Node node, final VisualSpiral visualSpiral) {
		
		this.node = node;
		this.visualSpiral = visualSpiral;
		this.arrows = new Vector<PPath>();
		
		this.xDimension = visualSpiral.getXDimension();
		this.yDimension = visualSpiral.getYDimension();
		
		for (Edge edge: node.getIncomingEdges()) {
			
			if (edge.getSourceNode().getDimension().equals(xDimension)) {
				for (Dimension valueDimension: edge.getValueDimensions()) {
					if (!values.containsKey(valueDimension)) {
						values.put(valueDimension, new ValuePair());
					}
					
					final ValuePair valuePair = values.get(valueDimension);
					valuePair.x = edge.getValue(valueDimension);
				}
			}
			
			if (edge.getSourceNode().getDimension().equals(yDimension)) {
				for (Dimension valueDimension: edge.getValueDimensions()) {
					if (!values.containsKey(valueDimension)) {
						values.put(valueDimension, new ValuePair());
					}
					
					final ValuePair valuePair = values.get(valueDimension);
					valuePair.y = edge.getValue(valueDimension);
				}
			}
		}
		
		visualSpiral.getAbstractView().getView().getSelection("standard")
		.addSelectionListener(node.getDimension(), this);
		
		PNode firstDot = null;
		for (final Dimension valueDimension: values.keySet()) {
			
			final ValuePair valuePair = values.get(valueDimension);
			
			if (!valuePair.isValid())
				continue;
	
			valuePair.dotNode = new PNode();
			addChild(valuePair.dotNode);
			
			if (firstDot == null) firstDot = valuePair.dotNode;
			
			PPath dot = new PPath();
			dot.append(new Rectangle2D.Double(-3, -3, 6, 6), false);
			dot.setPaint(Colors.getColorFor(node.getName(), "node"));
			dot.setStroke(null);
			dot.rotate(Math.toRadians(45));
			valuePair.dotNode.addChild(dot);
			
			final SpiralDescription sd = new SpiralDescription(this, valueDimension.getName(), 1.0);
			valuePair.dotNode.addChild(sd);
			this.descriptions.add(sd);
			
			final Blocker blocker = new Blocker(10);
			valuePair.dotNode.addChild(blocker);
			this.blockers.add(blocker);
		}
		
		SpiralDescription sd = new SpiralDescription(this, getLabel(), 0.1);
		sd.setFont(new Font("Arial", Font.BOLD, 14));
		this.descriptions.add(sd);
		
		if (firstDot != null)
			firstDot.addChild(sd);
		
		calculateStats();
		PPath color = new PPath(new Rectangle2D.Double(-4, -4, 8, 8));
		color.setPaint(Colors.getColorFor(node.getName(), "node"));
		color.setStroke(null);
		
		this.addInputEventListener(new PBasicInputEventHandler() {
			@Override
			public void mouseClicked(PInputEvent event) {
				super.mouseClicked(event);
				
				if (event.getClickCount() == 2) {
					visualSpiral.getCamera().animateViewToCenterBounds(Spiral.this.getBounds(), true, 1000);
				} else if (event.getClickCount() == 1) {
					if (Spiral.this.isSelected()) {
						visualSpiral.getAbstractView().getView()
						.getSelection("standard").unselect("TARGET", Spiral.this.getNode().getDimension());
						
					} else {
						visualSpiral.getAbstractView().getView()
						.getSelection("standard").select("TARGET", Spiral.this.getNode().getDimension());
					}
				}
			}
			
			@Override
			public void mouseEntered(PInputEvent event) {
				visualSpiral.showTooltip(Spiral.this, event.getPosition());
			}
			
			@Override
			public void mouseExited(PInputEvent event) {
				visualSpiral.hideTooltip();
			}
		});
		
		this.setSelected(false);
	}

	private void calculateStats() {
		int counterX = 0;
		int counterY = 0;
				
		avgX = 0;
		avgY = 0;
				
		for (final ValuePair valuePair: values.values()) {						
			
			if (valuePair.isValid()) {
				final double x = valuePair.x.getValue();
				final double y = valuePair.y.getValue();
				
				if (x < minX)
					minX = x;
				if (x > maxX)
					maxX = x;
				
				avgX += x;
				counterX++;
				
				if (y < minY) 
					minY = y;
				if (y > maxY)
					maxY = y;
				
				avgY += y;
				counterY++;
			}
		}
		
		avgX /= counterX;
		avgY /= counterY;
	}
	
	public double getAverageX () {
		return avgX;
	}
	
	public double getAverageY () {
		return avgY;
	}
	
	public double getMaxX () {
		return maxX;
	}
	
	public double getMaxY () {
		return maxY;
	}
	
	public double getMinX () {
		return minX;
	}
	
	public double getMinY () {
		return minY;
	}
	
	public void updatePositionInCamera(PCamera camera) {
		removeChildren(arrows);
		arrows.clear();
		
		this.setBounds(visualSpiral.xToProjection(minX-2), visualSpiral.yToProjection(minY-2), 
			visualSpiral.xToProjection(maxX-minX+5), visualSpiral.yToProjection(maxY-minY+5));
		
		Point2D lastPos = null;
		ValuePair lastValuePair = null;
				
		for (final Dimension valueDimension: visualSpiral.getGraph().getValueDimensions()) { 
			
			final ValuePair valuePair = values.get(valueDimension);
			if (valuePair == null || !valuePair.isValid()) continue;
			
			final PNode dot = valuePair.dotNode;
			
			Point2D pos = visualSpiral.newProjectionFrom(valuePair.toPoint2D());
			camera.viewToLocal(pos);
			
			dot.setGlobalTranslation(pos);
			if (camera.getBoundsReference().contains(pos)) {	
				dot.setVisible(true);
			} else {
				dot.setVisible(false);
			}

			if (lastValuePair != null) {
				lastPos = camera.viewToLocal(visualSpiral.newProjectionFrom(lastValuePair.toPoint2D()));

				if (camera.getBoundsReference().contains(pos) || camera.getBoundsReference().contains(lastPos)) {
					PPath arrow = new PPath();
					arrow.append(PPaths.createArrowShape(lastPos, pos, 10, 30), false);
					arrow.setStroke(ARROW_STROKE);
					arrow.setStrokePaint(Colors.getTransparentColorFor(node.getName(), "node"));
					addChild(arrow);
					arrows.add(arrow);
					arrow.moveToBack();
				}
			}
			
			lastValuePair = valuePair;
		}
	}

	public Node getNode () {
		return node;
	}

	public String getLabel() {
		return node.getName();
	}

	public Map<Dimension, ValuePair> getValues() {
		return Collections.unmodifiableMap(this.values);
	}
	
	public Dimension getXDimension() {
		return xDimension;
	}

	public Dimension getYDimension() {
		return yDimension;
	}

	public boolean isSelected() {
		return selected ;
	}
	
	private void setSelected (boolean selected) {
		this.selected = selected;
		
		for (SpiralDescription sd: this.descriptions) {
			sd.setVisible(selected);
		}
	}

	public List<SpiralDescription> getDescriptions() {
		return Collections.unmodifiableList(descriptions);
	}
	
	public List<DynamicallyPlaceable> getPlaceables() {
		List<DynamicallyPlaceable> result = new Vector<DynamicallyPlaceable>();
		result.addAll(descriptions);
		result.addAll(blockers);
		return result;
	}

	public VisualSpiral getVisualSpiral() {
		return this.visualSpiral;
	}

	@Override
	public void select(String category, Dimension dimension) {
		if ("TARGET".equals(category)) {
			this.setSelected(true);
		}
	}

	@Override
	public void unselect(String category, Dimension dimension) {
		if ("TARGET".equals(category)) {
			this.setSelected(false);
		}
	}

	@Override
	public void dispose() {
		visualSpiral.getAbstractView().getView().getSelection("standard")
		.removeSelectionListener(node.getDimension(), this);		
	}
}
